/**
 * 
 */
package uk.ac.ebi.velvet.util.loader;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;

import javolution.util.FastList;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.file.EntryMapper;
import uk.ac.ebi.curtain.utils.file.EntryMapperBean;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.file.ParseUtil;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.config.VelvetDataAccessService;
import uk.ac.ebi.velvet.model.ContigData;
import uk.ac.ebi.velvet.model.impl.ContigDataImpl;
import uk.ac.ebi.velvet.util.file.GlobalRandomFileAccess;
import uk.ac.ebi.velvet.util.io.SequenceUtil;
import uk.ac.ebi.velvet.util.io.WriteException;

/**
 * @author mhaimel
 *
 */
public class IndexContigDataLoader implements Callable<ContigData[]> {

	private Log log = LogFactory.getLog(this.getClass());
	private VelvetDataAccessService dataAccess;
	private final List<ContigData> ctgdata;
	private final ContigData[] dataArr;
	private int idxId = -1;
	private BufferedWriter out = null;
	private GlobalRandomFileAccess globalAccess = null;
	private File mappingFile = null;
	private Collection<FileInfo> contigFiles = new ArrayList<FileInfo>();
	
	public IndexContigDataLoader(VelvetDataAccessService data,int ctgCnt) {
		this.dataAccess = data;
		this.idxId = ctgCnt;
		this.ctgdata = new FastList<ContigData>();
		dataArr = new ContigData[ctgCnt];
		this.globalAccess = new GlobalRandomFileAccess(dataAccess);
	}
	
	public void setMappingFile(File file){
		mappingFile = file;
	}
	
	public void addContigFiles(Collection<FileInfo> ctgFiles){
		contigFiles.addAll(ctgFiles);
	}
	
	@Override
	public ContigData[] call() throws Exception {
		return loadAll();
	}
	
	public ContigData[] loadAll(){
		return load(contigFiles);
	}

	private ContigData[] load(Iterable<FileInfo> infoColl) throws WriteException{
		try {
			out = new FileIO(mappingFile).getBufferedWriter(); 
			DataMapper mapper = new DataMapper();
			for(FileInfo info : infoColl){
				getLog().info("Process file " + info);
				getGlobFileAccess().registerFile(info.getFile());
				ParseUtil parser = info.getFileType().getParser();
				parser.parse(info, mapper);
				getLog().debug("Finished processing file " + info);
			}	
		} catch (Exception e) {
			throw new WriteException("Problems writing to " + mappingFile,e);
		} finally{
			IOUtils.closeQuietly(out);
		}
		getLog().debug("Finished loading Contigs!");
		return mergeData();
	}
	
	private ContigData[] mergeData() {
		ContigData[] finalArr = Arrays.copyOf(this.dataArr, idxId);
		System.arraycopy(getContigData().toArray(), 0, finalArr, dataArr.length, getContigData().size());		
		return finalArr;
	}
	
	private GlobalRandomFileAccess getGlobFileAccess() {
		return globalAccess;
	}
	
	private void addEntry(EntryMapperBean data){
		//id, SequenceUtil.clearDNA(sequence).length());	
		String id = data.getId();
		int length = SequenceUtil.clearDNA(data.getSequence()).length();	
		if(StringUtils.isNotBlank(id) && length > 0){
			Integer currIdx = getIdx(id);
			if(null == currIdx){
				currIdx = idxId++;
			}
			ContigDataImpl currRef = new ContigDataImpl(currIdx,length);
			currRef.setFilePosition(
					getGlobFileAccess().translatePosition(data.getInfo().getFile(), data.getFilePosition()));
			try {
				out.write(id + "\t"+currIdx+"\n");
			}catch (IOException e) {
				throw new WriteException(e);
			}
			
			if(currIdx < dataArr.length){
				getReferenceArr()[currIdx] = currRef;
			} else {
				getContigData().add(currRef);
			}
		}
	}

	private Integer getIdx(String currIdStr) {
		return this.dataAccess.getContigIdentifier(currIdStr);
	}

	private List<ContigData> getContigData() {
		return this.ctgdata;
	}	
	
	private ContigData[] getReferenceArr() {
		return this.dataArr;
	}	
	
	private class DataMapper implements EntryMapper{

		@Override
		public void mapEntry(EntryMapperBean data) {
			addEntry(data); 				
		}
	}
	
	private Log getLog() {
		return log;
	}

}
